﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Behaviorize.Properties;
using Behaviorize.Reporting.Model;
using Behaviorize.TextFormatting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Behaviorize.Reporting
{
	public class ReportMaker
	{
		#region Singleton

		private static ReportMaker _instance;

		public static ReportMaker Instance
		{
			get { return _instance ?? (_instance = new ReportMaker()); }
		}

		#endregion

		private bool _orphansRemoved;
		private static readonly object FileLock = new object();
		private readonly Queue<Action> _jobs = new Queue<Action>();

		private static string JsonFilename
		{
			get { return ReportDirectory + "js\\Data.json"; }
		}

		private static string HtmlFilename
		{
			get { return ReportDirectory + "Report.html"; }
		}

		private static string ReportDirectory
		{
			get { return Environment.CurrentDirectory + "\\BehaviorizeReport\\"; }
		}

		private void SaveReport(Report report)
		{
			using (StringWriter stringWriter = new StringWriter())
			using (JsonTextWriter jsonWriter = new JsonTextWriter(stringWriter)
				{
					IndentChar = '\t', 
					Indentation = 1, 
					Formatting = Formatting.Indented
				})
			{
				JsonSerializer serializer = new JsonSerializer();

				serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
				serializer.Serialize(jsonWriter, report);

				string json = stringWriter.ToString();
				string html = Resources.Report.Replace("/*json*/", "var json = " + json + ";");
				File.WriteAllText(HtmlFilename, html);
				File.WriteAllText(JsonFilename, json);
			}
		}

		private void DoWork()
		{
			while (_jobs.Count > 0)
			{
				Action job = _jobs.Dequeue();
				job.Invoke();
			}
		}

		private Report GetReport(string assemblyName)
		{
			string jsonString;

			try
			{
				jsonString = File.ReadAllText(JsonFilename);
			}
			catch
			{
				jsonString = null;
			}

			Report report;

			try
			{
				report = (!String.IsNullOrEmpty(jsonString)
				          	? JsonConvert.DeserializeObject<Report>(jsonString)
				          	: new Report())
				         ?? new Report();
			}
			catch (Exception e)
			{
				Console.WriteLine(e);
				report = new Report();
			}

			report.Assembly = assemblyName;

			return report;
		}

		private static void RemoveOrhpanFeaturesAndScenarios(Report report, Assembly testAssembly)
		{
			Type[] typesInAssembly = testAssembly.GetTypes();
			List<Action> namespaceRemovalActions = new List<Action>();

			foreach (Namespace ns in report.Namespaces)
			{
				CleanNamespace(ns, typesInAssembly, namespaceRemovalActions);
			}

			namespaceRemovalActions.ForEach(action => action());

			for (int i = report.Features.Count - 1; i >= 0; i--)
			{
				Model.Feature feature = report.Features[i];
				if (!typesInAssembly.Any(tc => tc.Name == feature.ClassName))
				{
					report.Features.RemoveAt(i);
				}
			}
		}

		private static void CleanNamespace(Namespace ns, Type[] typesInAssembly, List<Action> namespaceRemovalActions)
		{
			for (int i = ns.Features.Count - 1; i >= 0; i--)
			{
				Model.Feature feature = ns.Features[i];
				if (!typesInAssembly.Any(tc => tc.Name == feature.ClassName && NamespaceEquals(tc.Namespace, ns.Name)))
				{
					ns.Features.RemoveAt(i);
				}
			}
			
			foreach (Namespace subNs in ns.Namespaces)
			{
				CleanNamespace(subNs, typesInAssembly, namespaceRemovalActions);
				if (subNs.Features.Count == 0)
				{
					Namespace namespaceToRemove = subNs;
					List<Namespace> collection = ns.Namespaces;
					namespaceRemovalActions.Add(() => collection.Remove(namespaceToRemove));
				}
			}
		}

		private static bool NamespaceEquals(string fullNamespace, string leafNamespace)
		{
			string[] namespaceNodes = fullNamespace.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
			return namespaceNodes[namespaceNodes.Length - 1] == leafNamespace;
		}

		public void CreateFeature(Feature feature)
		{
			_jobs.Enqueue(() =>
				{
					lock (FileLock)
					{
						Report report = GetReport(feature.Assembly.GetName().Name);
						report.CreateFeature(feature);

						if (!_orphansRemoved)
						{
							RemoveOrhpanFeaturesAndScenarios(report, feature.Assembly);
							_orphansRemoved = true;
						}

						SaveReport(report);
					}
				});
		}

		internal static string FormatStepText(Step testStep, bool firstStepInClause)
		{
			string prefix = firstStepInClause ? testStep.ClauseName : "and";
			return prefix + TextFormatter.ToText(testStep.StepDelegate.Method.Name, true, testStep.Arguments);
		}

		public void CreateReportForScenario(Feature feature, string scenarioName, List<ScenarioVersion> scenarios)
		{
			_jobs.Enqueue(() =>
			{
				lock (FileLock)
				{
					Report report = GetReport(feature.Assembly.GetName().Name);
					report.CreateScenarioReport(feature, scenarioName, scenarios);
					SaveReport(report);
				}
			});

			DoWork();
		}
	}
}